﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using EventFlowLibrary.Player;
using System.Xml;
using PerformanceUtility.GameDebugTools;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Audio;
using EventFlowLibrary.LevelComponents;
using System.Globalization;

namespace EventFlowLibrary.GameStructure
{
    /// <summary>
    /// A frame is a block of text, that can be made of many text screens that are 
    /// displayed in the same dialogue segment.
    /// Text is keyed by entityID, storyNodeID and frameID; in that way, we retrieve 
    /// based on an entity and node, and the frame ID increments according to the rules.
    /// If an entity in a particular node has several sequential frames and repeats the last,
    /// we can model that by incrementing the current frame until we hit the last, then looping.
    /// </summary>
    internal sealed class TextManagement : DrawableGameComponent
    {
        /// <summary>
        /// Containing world.
        /// </summary>
        World world;

        /// <summary>
        /// The XML file containing all relevant text information.
        /// </summary>
        private string xmlFile;

        /// <summary>
        /// Spritebatch for drawing materials.
        /// </summary>
        private SpriteBatch spriteBatch;

        /// <summary>
        /// The dictionary for all textures used as background/foreground text components.
        /// </summary>
        private Dictionary<string, Texture2D> usedTextures;

        /// <summary>
        /// The dictionary containing all fonts used.
        /// </summary>
        private Dictionary<string, SpriteFont> usedFonts;

        /// <summary>
        /// Text events managed by the manager.
        /// </summary>
        internal Dictionary<string, TextEvent> TextEvents { get { return textEvents; } }
        private Dictionary<string, TextEvent> textEvents;

        /// <summary>
        /// The queue of current text events to be displayed.
        /// Only one is displayed at a time.
        /// </summary>
        internal List<TextEvent> CurrentEvents { get { return currentEvents; } }
        private List<TextEvent> currentEvents;

        /// <summary>
        /// The current index of frames inside the currently displaying eve.t
        /// </summary>
        private int currentFrameIndex = 0;

        /// <summary>
        /// Index for the current framepart for the displaying event and frame.
        /// </summary>
        private int currentPartIndex = 0;

        /// <summary>
        /// The string we are currently displaying.
        /// </summary>
        private StringBuilder displayString;

        /// <summary>
        /// Milliseconds since string was updated using "Type" style.
        /// </summary>
        private int millisecondsElapsed = 0;

        /// <summary>
        /// If the indicator is 0, the topmost event is closing; if 1, it is opening.
        /// </summary>
        private bool statusIndicator;

        /// <summary>
        /// How much previous frame parts have offset the text so far width wise.
        /// </summary>
        private float textWidthOffset = 0.0f;

        /// <summary>
        /// How many new line characters in the frame parts so far.
        /// </summary>
        private float textHeightOffset = 0.0f;

        /// <summary>
        /// Used to fade in the button.
        /// </summary>
        private float alphaFade = 0.0f;

        /// <summary>
        /// How fast the button fades in.
        /// </summary>
        private const float fadeRate = 500.0f;

        /// <summary>
        /// Type sound variables
        /// </summary>
        private int timeBetween;
        private int currentTimeBetween;
        Random rand = new Random();
        private const string newLineCharacter = "\n";

        /// <summary>
        /// A reviewer asked to fade the sound after the first few, in order to not be so irritating (continuous noise)
        /// </summary>
        private float vol;
        private const int fadeSoundAt = 10;
        private int soundsSoFar = 0;

        /// <summary>
        /// A single letter framework for displaying all letters.
        /// </summary>
        internal Letter letter;

        /// <summary>
        /// Create a new set of text by loading it from an XML file.
        /// </summary>
        /// <param name="_XMLFile">The XML containing the text to be loaded.</param>
        internal TextManagement(Game game, World _world, string _XMLFile) : base(game)
        {
            // Track our container
            world = _world;

            // To be loaded in LoadContent
            xmlFile = _XMLFile;

            // Track sound timing
            timeBetween = rand.Next(50, 200);
            currentTimeBetween = 0;

            // Add and create the letter framework
            letter = new Letter(this.Game, this.world.screenManager, this);
            this.Game.Components.Add(letter); // we remove this later so it can be drawn under text events
        }

        /// <summary>
        /// Initialize the component.
        /// </summary>
        public override void  Initialize()
        {
            // Prep dictionaries
            usedTextures = new Dictionary<string, Texture2D>();
            usedFonts = new Dictionary<string, SpriteFont>();
            textEvents = new Dictionary<string, TextEvent>();
            currentEvents = new List<TextEvent>();

            spriteBatch = new SpriteBatch(this.Game.GraphicsDevice);

            // Add debug controls
            //DebugSystem.Instance.DebugCommandUI.RegisterCommand("deq", "Dequeue text event: deq [id] [true/false all occurences]", this.DequeueCommandExecute);
            //DebugSystem.Instance.DebugCommandUI.RegisterCommand("enq", "Enqueue text event: enq [id] [true/false allow duplicates]", this.EnqueueCommandExecute);
            //DebugSystem.Instance.DebugCommandUI.RegisterCommand("tev", "Discover text event status", this.QueueCountCommandExecute);

            // Prep our display string
            displayString = new StringBuilder(1024, 1024);

 	        base.Initialize();
        }

        /// <summary>
        /// Load the used content items.
        /// </summary>
        protected override void  LoadContent()
        {
            // Create the content manager
            ContentManager content = new ContentManager(this.Game.Content.ServiceProvider, "Content");

            // Load the XML file
            XElement xel = XElement.Load(xmlFile);
            XmlReader reader;
            reader = xel.CreateReader();

            // Read the info
            while (reader.Read())
            {
                // Check we've found an element. Use a "switch" statement to access if we need more.
                if (reader.NodeType == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        case "Font":
                            // Load used fonts
                            usedFonts.Add(reader["ID"].ToString(), content.Load<SpriteFont>(reader["Path"].ToString())); break;
                        case "Texture":
                            // Load used textures
                            usedTextures.Add(reader["ID"].ToString(), content.Load<Texture2D>(reader["Path"].ToString())); break;
                        case "TextEvent":
                            // Construct the percentage rectangle
                            string currentID = reader["ID"].ToString();
                            string[] topleft = reader["TopLeft"].ToString().Split(',');
                            string[] botright = reader["Dimensions"].ToString().Split(',');
                            Rectangle rect = new Rectangle(Convert.ToInt32(topleft[0], CultureInfo.InvariantCulture), Convert.ToInt32(topleft[1], CultureInfo.InvariantCulture), Convert.ToInt32(botright[0], CultureInfo.InvariantCulture), Convert.ToInt32(botright[1], CultureInfo.InvariantCulture));

                            // Construct color
                            string[] esc1 = reader["BackgroundColor"].ToString().Split(',');
                            Color ec1 = new Color(Convert.ToInt32(esc1[0], CultureInfo.InvariantCulture), Convert.ToInt32(esc1[1], CultureInfo.InvariantCulture), Convert.ToInt32(esc1[2], CultureInfo.InvariantCulture), Convert.ToInt32(esc1[3], CultureInfo.InvariantCulture));

                            // Create the event
                            textEvents.Add(reader["ID"].ToString(), new TextEvent(reader["ID"].ToString(), reader["Background"].ToString(), ec1, reader["Foreground"].ToString(),
                                           (OpeningType)Enum.Parse(typeof(OpeningType), reader["BackgroundOpening"].ToString(), false), Convert.ToInt32(reader["StartDim"].ToString(), CultureInfo.InvariantCulture),
                                           Convert.ToInt32(reader["OpenSpeed"].ToString(), CultureInfo.InvariantCulture), Convert.ToInt32(reader["CloseSpeed"].ToString(), CultureInfo.InvariantCulture), 
                                           this.Game.GraphicsDevice.Viewport, rect));

                            // Loop and load the frames. We will always have at least one.
                            while (reader.Read())
                            {
                                // Start loading our new text frame
                                if (reader.Name == "TextFrame" && reader.NodeType == XmlNodeType.Element)
                                {
                                    string[] bl = reader["ButtonLocation"].ToString().Split(',');
                                    string[] bs = reader["ButtonSize"].ToString().Split(',');

                                    // Get the screen location of the button's center
                                    int x = Convert.ToInt32(bs[0], CultureInfo.InvariantCulture);
                                    int y = Convert.ToInt32(bs[1], CultureInfo.InvariantCulture);
                                    int x1 = (int)(this.Game.GraphicsDevice.Viewport.Width * (float)(Convert.ToInt32(bl[0], CultureInfo.InvariantCulture) / 100.0f)) - (x / 2);
                                    int y1 = (int)(this.Game.GraphicsDevice.Viewport.Height * (float)(Convert.ToInt32(bl[1], CultureInfo.InvariantCulture) / 100.0f)) - (y / 2);

                                    textEvents[currentID].AddFrame(reader["Font"].ToString(), reader["ContinueButton"].ToString(), Convert.ToBoolean(reader["CanSkip"].ToString(), CultureInfo.InvariantCulture), new Vector2(x1, y1), new Vector2(x, y), Convert.ToInt32(reader["AutoContinueTime"].ToString(), CultureInfo.InvariantCulture), Convert.ToBoolean(reader["EndSound"].ToString(), CultureInfo.InvariantCulture));

                                    // Loop and load the frame parts. We will always have at least one.
                                    while (reader.Read())
                                    {
                                        if (reader.Name == "FramePart" && reader.NodeType == XmlNodeType.Element)
                                        {
                                            string[] sc1 = reader["Color"].ToString().Split(',');
                                            Color c1 = new Color(Convert.ToInt32(sc1[0], CultureInfo.InvariantCulture), Convert.ToInt32(sc1[1], CultureInfo.InvariantCulture), Convert.ToInt32(sc1[2], CultureInfo.InvariantCulture), Convert.ToInt32(sc1[3], CultureInfo.InvariantCulture));
                                            string[] sc2 = reader["ShadowColor"].ToString().Split(',');
                                            Color c2 = new Color(Convert.ToInt32(sc2[0], CultureInfo.InvariantCulture), Convert.ToInt32(sc2[1], CultureInfo.InvariantCulture), Convert.ToInt32(sc2[2], CultureInfo.InvariantCulture), Convert.ToInt32(sc2[3], CultureInfo.InvariantCulture));

                                            textEvents[currentID].TextFrames[textEvents[currentID].TextFrames.Length - 1].AddFramePart(c1, (TypeDelivery)Enum.Parse(typeof(TypeDelivery), reader["TypeDelivery"].ToString(), false),
                                                Convert.ToInt32(reader["TimeBetweenChars"].ToString(), CultureInfo.InvariantCulture), (TypeDecoration)Enum.Parse(typeof(TypeDecoration), reader["TextDecoration"].ToString(), false),
                                                Convert.ToInt32(reader["ShadowOffset"].ToString(), CultureInfo.InvariantCulture), c2, Convert.ToBoolean(reader["TypeSound"].ToString(), CultureInfo.InvariantCulture), reader["Text"].ToString().Replace("\\n", "\n"));
                                        }

                                        // Skip out of this text frame when we've reached the end
                                        if (reader.Name == "TextFrame" && reader.NodeType == XmlNodeType.EndElement)
                                        {
                                            break;
                                        }
                                    }
                                }

                                // Skip out of this text event when we've reached the end
                                if (reader.Name == "TextEvent" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }
                            }
                            break;
                    }
                }
            }

            reader.Close();
            
            // Add button textures
            usedTextures.Add("AButton", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonA"));
            usedTextures.Add("BButton", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonB"));
            usedTextures.Add("XButton", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonX"));
            usedTextures.Add("YButton", content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonY"));

 	        base.LoadContent();

            this.Game.Components.Remove(letter);
        }

        /// <summary>
        /// Draw the visible text and textures.
        /// </summary>
        /// <param name="gameTime">Current game time.</param>
        public override void Draw(GameTime gameTime)
        {
            letter.Draw(gameTime);

            // Draw the topmost item
            if (currentEvents.Count > 0)
            {
                spriteBatch.Begin();

                // Reset the text offset
                textWidthOffset = 0.0f;
                textHeightOffset = 4.0f;    // Push down 4 pixels for this game's border

                // Always draw an opening/closing background texture
                spriteBatch.Draw(usedTextures[currentEvents[0].BackgroundTexture], currentEvents[0].CurrentDimensions, currentEvents[0].BackgroundColor);

                // Draw the text and overlay texture if fully expanded
                if (currentEvents[0].CurrentDimensions == currentEvents[0].DesiredOpenDimensions)
                {
                    // Display up to the current frame/part. The current frame/part compose the displayString variable.
                    // This "for" loop draws the completed frame parts. Below this, the part being typed out is displayed.
                    for (int i = 0; i < currentPartIndex; i++)
                    {
                        // Draw text decoration if we have it
                        if (currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].Decoration == TypeDecoration.Shadow)
                        {
                            spriteBatch.DrawString(usedFonts[currentEvents[0].TextFrames[currentFrameIndex].Font], currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].Text,
                                new Vector2(currentEvents[0].CurrentDimensions.Left + currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].ShadowOffset + textWidthOffset, currentEvents[0].CurrentDimensions.Top + currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].ShadowOffset + textHeightOffset),
                                currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].ShadowColor);
                        }

                        // Draw the text
                        spriteBatch.DrawString(usedFonts[currentEvents[0].TextFrames[currentFrameIndex].Font], currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].Text,
                            new Vector2(currentEvents[0].CurrentDimensions.Left + textWidthOffset, currentEvents[0].CurrentDimensions.Top + textHeightOffset),
                            currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].PartColor);

                        // Increment the offset by the current text of all previous non-newline parts displayed so far.
                        textWidthOffset += usedFonts[currentEvents[0].TextFrames[currentFrameIndex].Font].MeasureString(currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].Text).X;

                        // New lines must be by themselves and reset the values
                        if (currentEvents[0].TextFrames[currentFrameIndex].FrameParts[i].Text == newLineCharacter)
                        {
                            textHeightOffset += usedFonts[currentEvents[0].TextFrames[currentFrameIndex].Font].LineSpacing;
                            textWidthOffset = 0;
                        }
                    }

                    // These following statements are for drawing the pieces of the current framepart we are in, constructed in Update.
                    // Draw text decoration if we have it
                    if (currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Decoration == TypeDecoration.Shadow)
                    {
                        spriteBatch.DrawString(usedFonts[currentEvents[0].TextFrames[currentFrameIndex].Font], displayString,
                            new Vector2(currentEvents[0].CurrentDimensions.Left + currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].ShadowOffset + textWidthOffset, currentEvents[0].CurrentDimensions.Top + currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].ShadowOffset + textHeightOffset),
                            currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].ShadowColor);
                    }

                    // Draw the text
                    spriteBatch.DrawString(usedFonts[currentEvents[0].TextFrames[currentFrameIndex].Font], displayString,
                        new Vector2(currentEvents[0].CurrentDimensions.Left + textWidthOffset, currentEvents[0].CurrentDimensions.Top + textHeightOffset),
                        currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].PartColor);
                }

                // Always draw an opening/closing foregrond texture
                if (currentEvents[0].ForegroundTexture != "")
                {
                    spriteBatch.Draw(usedTextures[currentEvents[0].ForegroundTexture], currentEvents[0].CurrentDimensions, Color.White);
                }

                // We finished drawing the last part, and are at the last stage so draw the button.
                if (currentEvents[0].TextFrames[currentFrameIndex].FrameParts.Length - 1 == currentPartIndex &&
                    currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Text == displayString.ToString())
                {
                    if (currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.A))
                    {
                        spriteBatch.Draw(usedTextures["AButton"], new Rectangle((int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.Y, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.Y), Color.White * alphaFade);
                    }
                    else if (currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.B))
                    {
                        spriteBatch.Draw(usedTextures["BButton"], new Rectangle((int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.Y, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.Y), Color.White);
                    }
                    else if (currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.X))
                    {
                        spriteBatch.Draw(usedTextures["XButton"], new Rectangle((int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.Y, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.Y), Color.White);
                    }
                    else if (currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.Y))
                    {
                        spriteBatch.Draw(usedTextures["YButton"], new Rectangle((int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonLocation.Y, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.X, (int)currentEvents[0].TextFrames[currentFrameIndex].ButtonSize.Y), Color.White);
                    }
                }

                spriteBatch.End();
            }

            //base.Draw(gameTime);
        }

        /// <summary>
        /// Update the game.
        /// </summary>
        /// <param name="gameTime">Current game time.</param>
        public override void Update(GameTime gameTime)
        {
            // Update the topmost item
            if (currentEvents.Count > 0)
            {
                // Track how long since our last letter type
                if (currentEvents[0].CurrentDimensions == currentEvents[0].DesiredOpenDimensions)
                {
                    // Load up any immediate text that is necessary, checking the last frame as well
                    while (currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Delivery == TypeDelivery.Immediate
                        && currentPartIndex < currentEvents[0].TextFrames[currentFrameIndex].FrameParts.Length - 1)
                    {
                        currentPartIndex++;
                    }
                    if (currentPartIndex == currentEvents[0].TextFrames[currentFrameIndex].FrameParts.Length - 1
                        && currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Delivery == TypeDelivery.Immediate)
                    {
                        displayString.Remove(0, displayString.Length);
                        displayString.Append(currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Text);
                    }

                    currentTimeBetween += gameTime.ElapsedGameTime.Milliseconds;

                    // Increment time, or update dispay text
                    if (millisecondsElapsed < currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].TimeBetweenChars)
                    {
                        // Sometimes, for super fast games, we get elapsed time of 0 milliseconds, so use this Max function
                        //millisecondsElapsed += Math.Max(1, gameTime.ElapsedGameTime.Milliseconds);
                        millisecondsElapsed += gameTime.ElapsedGameTime.Milliseconds;
                    }
                    else
                    {
                        // Play the type sound if indicated. Don't play sound for spaces.
                        if (currentTimeBetween >= timeBetween)
                        {
                            timeBetween = rand.Next(50, 200);
                            currentTimeBetween = 0;

                            if (currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].TypeSound &&
                                displayString.ToString() != currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Text)
                            {
                                vol = 0.35f;
                                // Fade the sound at the "fadeSoundAt" for the length of the "fadeSoundAt" variable
                                // TODO all very inneficient
                                if (soundsSoFar >= fadeSoundAt)
                                {
                                    vol = MathHelper.Max(0, MathHelper.Min(vol, vol * (1 - (soundsSoFar / (float)(fadeSoundAt + fadeSoundAt)))));
                                }
                                AudioManager.soundEffects["typeSound"].PlaySound(vol, (float)rand.NextDouble() / 12, 0, false);
                                soundsSoFar++;
                            }
                        }

                        // Change frame and part if we can; else, get another letter.
                        // The while loop allows "fast" typing in low framerate situations - for example, if
                        //      we are trying to type every 5ms, but are running at 10fps (100ms between frames),
                        //      without the look, at best, we get a letter 10 times a second (instead of 200 @ 5ms per).
                        //      Therefore, fit as many as milliseconds elapsed allows.
                        //      This does allow us to force too many (for example, one letter ever 5ms, with 6 ms per frame, will display twice)
                        //      but it is way less irritating to the user than super-slow nonskippable text.
                        while (millisecondsElapsed > 0)
                        {
                            if (displayString.Length == currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Text.Length)
                            {
                                // Increment to the next frame part if we can.
                                if (currentEvents[0].TextFrames[currentFrameIndex].FrameParts.Length - 1 != currentPartIndex)
                                {
                                    currentPartIndex++;
                                    displayString.Remove(0, displayString.Length);
                                }
                            }
                            else
                            {
                                displayString.Append(currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Text.Substring(displayString.Length, 1));
                            }

                            millisecondsElapsed -= currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].TimeBetweenChars;
                        }

                        millisecondsElapsed = 0;
                    }
                }

                // If we are opening, use opening speed; else, use closing. Clamp within current range.
                if (statusIndicator == true)
                {
                    if (currentEvents[0].CurrentDimensions != currentEvents[0].DesiredOpenDimensions)
                    {

                        switch (currentEvents[0].BackgroundOpening)
                        {
                            case OpeningType.Immediate:
                                currentEvents[0].CurrentDimensions = currentEvents[0].DesiredOpenDimensions; break;
                            case OpeningType.ScrollDown:
                                currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left, currentEvents[0].CurrentDimensions.Top, currentEvents[0].CurrentDimensions.Width, currentEvents[0].CurrentDimensions.Height + (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds));
                                if (currentEvents[0].CurrentDimensions.Height > currentEvents[0].DesiredOpenDimensions.Height)
                                    currentEvents[0].CurrentDimensions = currentEvents[0].DesiredOpenDimensions; break;
                            case OpeningType.ScrollLeft:
                                currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left - (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Top, currentEvents[0].CurrentDimensions.Width + (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Height);
                                if (currentEvents[0].CurrentDimensions.Left < currentEvents[0].DesiredOpenDimensions.Left)
                                    currentEvents[0].CurrentDimensions = currentEvents[0].DesiredOpenDimensions; break;
                            case OpeningType.ScrollRight:
                                currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left, currentEvents[0].CurrentDimensions.Top, currentEvents[0].CurrentDimensions.Width + (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Height);
                                if (currentEvents[0].CurrentDimensions.Width > currentEvents[0].DesiredOpenDimensions.Width)
                                    currentEvents[0].CurrentDimensions = currentEvents[0].DesiredOpenDimensions; break;
                            case OpeningType.ScrollUp:
                                currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left, currentEvents[0].CurrentDimensions.Top - (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Width, currentEvents[0].CurrentDimensions.Height + (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds));
                                if (currentEvents[0].CurrentDimensions.Top < currentEvents[0].DesiredOpenDimensions.Top)
                                    currentEvents[0].CurrentDimensions = currentEvents[0].DesiredOpenDimensions; break;
                        }
                    }
                }
                else
                {
                    switch (currentEvents[0].BackgroundOpening)
                    {
                        case OpeningType.Immediate:
                            currentEvents[0].CurrentDimensions = currentEvents[0].DesiredClosedDimensions; break;
                        case OpeningType.ScrollDown:
                            currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left, currentEvents[0].CurrentDimensions.Top, currentEvents[0].CurrentDimensions.Width, currentEvents[0].CurrentDimensions.Height - (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds));
                            if (currentEvents[0].CurrentDimensions.Height < currentEvents[0].StartDim)
                                currentEvents[0].CurrentDimensions = currentEvents[0].DesiredClosedDimensions; break;
                        case OpeningType.ScrollLeft:
                            currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left + (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Top, currentEvents[0].CurrentDimensions.Width - (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Height);
                            if (currentEvents[0].CurrentDimensions.Width < currentEvents[0].StartDim)
                                currentEvents[0].CurrentDimensions = currentEvents[0].DesiredClosedDimensions; break;
                        case OpeningType.ScrollRight:
                            currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left, currentEvents[0].CurrentDimensions.Top, currentEvents[0].CurrentDimensions.Width - (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Height);
                            if (currentEvents[0].CurrentDimensions.Width < currentEvents[0].StartDim)
                                currentEvents[0].CurrentDimensions = currentEvents[0].DesiredClosedDimensions; break;
                        case OpeningType.ScrollUp:
                            currentEvents[0].CurrentDimensions = new Rectangle(currentEvents[0].CurrentDimensions.Left, currentEvents[0].CurrentDimensions.Top + (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds), currentEvents[0].CurrentDimensions.Width, currentEvents[0].CurrentDimensions.Height - (int)(currentEvents[0].OpenSpeed * gameTime.ElapsedGameTime.TotalMilliseconds));
                            if (currentEvents[0].CurrentDimensions.Height < currentEvents[0].StartDim)
                                currentEvents[0].CurrentDimensions = currentEvents[0].DesiredClosedDimensions; break;
                    }

                    // Set to open the next and remove if finished reducing
                    if (currentEvents[0].CurrentDimensions == currentEvents[0].DesiredClosedDimensions)
                    {
                        statusIndicator = true;
                        currentEvents.RemoveAt(0);
                        currentFrameIndex = 0;
                        currentPartIndex = 0;
                        displayString.Remove(0, displayString.Length);
                    }
                }
            }

            base.Update(gameTime);
        }

        public void HandleInput(GameTime gameTime, InputState _inputState)
        {
            // Hide letter if shown
            if (letter.IsShown)
            {
#if WINDOWS
                if (_inputState.IsNewKeyPress(Keys.B, GlobalVariables.playerIndex) && this.currentEvents.Count == 0)
#else
                if (_inputState.IsNewButtonPress(Buttons.B, GlobalVariables.playerIndex) && this.currentEvents.Count == 0)
#endif
                {
                    letter.IsShown = false;
                }
            }

            // Check if we can skip or continue to the next frame
            if (currentEvents.Count > 0)
            {
                // Check if frame is fully displayed
                if (currentEvents[0].TextFrames[currentFrameIndex].FrameParts.Length - 1 == currentPartIndex &&
                    currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Text == displayString.ToString())
                {
                    alphaFade += Math.Max(1, gameTime.ElapsedGameTime.Milliseconds) / fadeRate;

                    // Try to continue to the next frame, or exit the event else
#if WINDOWS
                    if ((_inputState.IsNewKeyPress(Keys.A, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.A)) ||
                        (_inputState.IsNewKeyPress(Keys.B, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.B)) ||
                        (_inputState.IsNewKeyPress(Keys.X, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.X)) ||
                        (_inputState.IsNewKeyPress(Keys.Y, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.Y)))
#else
                    if ((_inputState.IsNewButtonPress(Buttons.A, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.A)) ||
                        (_inputState.IsNewButtonPress(Buttons.B, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.B)) ||
                        (_inputState.IsNewButtonPress(Buttons.X, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.X)) ||
                        (_inputState.IsNewButtonPress(Buttons.Y, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.Y)))
#endif
                    {
                        // Play sound at the end
                        if (currentEvents[0].TextFrames[currentFrameIndex].EndSound)
                        {
                            AudioManager.soundEffects["typeEndSound"].PlaySound(1.0f, 0, 0, false);
                        }

                        // Exit the event
                        if (currentFrameIndex == currentEvents[0].TextFrames.Length - 1)
                        {
                            statusIndicator = false;
                            soundsSoFar = 0;
                            currentFrameIndex = 0;
                            currentPartIndex = 0;
                            alphaFade = 0.0f;
                            displayString.Remove(0, displayString.Length);
                        }
                        // Load the next frame
                        else
                        {
                            currentFrameIndex++;
                            soundsSoFar = 0;
                            currentPartIndex = 0;
                            alphaFade = 0.0f;
                            displayString.Remove(0, displayString.Length);
                        }
                    }
                }   // End frame fully displayed - Check whether we can skip to the end of the current frame
                else if (currentEvents[0].TextFrames[currentFrameIndex].CanSkip)
                {
#if WINDOWS
                    if ((_inputState.IsNewKeyPress(Keys.A, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.A)) ||
                        (_inputState.IsNewKeyPress(Keys.B, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.B)) ||
                        (_inputState.IsNewKeyPress(Keys.X, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.X)) ||
                        (_inputState.IsNewKeyPress(Keys.Y, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.Y)))
#else
                    if ((_inputState.IsNewButtonPress(Buttons.A, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.A)) ||
                        (_inputState.IsNewButtonPress(Buttons.B, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.B)) ||
                        (_inputState.IsNewButtonPress(Buttons.X, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.X)) ||
                        (_inputState.IsNewButtonPress(Buttons.Y, GlobalVariables.playerIndex) &&
                        currentEvents[0].TextFrames[currentFrameIndex].ContinueButtons.Contains(Buttons.Y)))
#endif
                    {
                        currentPartIndex = currentEvents[0].TextFrames[currentFrameIndex].FrameParts.Length - 1;
                        displayString.Remove(0, displayString.Length);
                        displayString.Append(currentEvents[0].TextFrames[currentFrameIndex].FrameParts[currentPartIndex].Text);
                    }
                }   // End can skip
            }
        }

        /// <summary>
        /// Queue up a text event to display.
        /// </summary>
        /// <param name="_id">The event of the text event to display.</param>
        /// <param name="_allowDuplicates">If true, insert into queue even if already present in queue.</param>
        public void EnqueueTextEvent(string _id, bool _allowDuplicates)
        {
#if TRACE
            //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent("Enqueued text event: " + _id + " " + _allowDuplicates.ToString());
#endif
            if (_allowDuplicates || !currentEvents.Contains(textEvents[_id]))
            {
                statusIndicator = true;
                currentEvents.Add(textEvents[_id]);
            }
        }

        /// <summary>
        /// Removes either the first or all of the events in the queue.
        /// </summary>
        /// <param name="_id">The text event to dequeue.</param>
        /// <param name="_includeDuplicates">Whether to remove the first only (false) or all (true).</param>
        public void DequeueTextEvent(string _id, bool _includeDuplicates)
        {
#if TRACE
            //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent("Dequeued text event: " + _id + " " + _includeDuplicates.ToString());
#endif
            if (_includeDuplicates)
            {
                Stack<int> indicesToRemove = new Stack<int>();

                // Remove all occurences
                for (int i = 0; i < currentEvents.Count; i++)
                {
                    if (currentEvents[i].ID == _id)
                    {
                        indicesToRemove.Push(i);
                    }
                }

                for (int i = indicesToRemove.Count; i >= 0; i--)
                {
                    currentEvents.RemoveAt(indicesToRemove.Pop());
                }
            }
            else
            {
                // Remove the first occurence
                for (int i = 0; i < currentEvents.Count; i++)
                {
                    if (currentEvents[i].ID == _id)
                    {
                        currentEvents.Remove(currentEvents[i]);
                        break;
                    }
                }
            }
        }

        internal void SetLetter(string _letter)
        {
            letter.shownLetter = _letter;
            letter.IsShown = true;
        }

#if TRACE
        void EnqueueCommandExecute(IDebugCommandHost host, string command, IList<string> arguments)
        {
            // Empty list if nothing specified.
            if (arguments.Count == 0)
            {
                host.Echo("Please specify a text event to enqueue...");
            }
            else 
            {
                if (textEvents.ContainsKey(arguments[0]))
                {
                    if (arguments.Count == 1)
                    {
                        // Default don't allow duplicates
                        EnqueueTextEvent(arguments[0], false);
                    }
                    else if (arguments.Count == 2)
                    {
                        if (arguments[1] == "true" || arguments[1] == "false")
                        {
                            EnqueueTextEvent(arguments[0], Convert.ToBoolean(arguments[1], CultureInfo.InvariantCulture));
                        }
                        else
                        {
                            EnqueueTextEvent(arguments[0], true);
                        }
                    }
                }
                else
                {
                    host.EchoError("ID does not exist in loaded text events.");
                }
            }
        }

        void DequeueCommandExecute(IDebugCommandHost host, string command, IList<string> arguments)
        {
            // Empty list if nothing specified.
            if (arguments.Count == 0)
            {
                statusIndicator = false;
                currentFrameIndex = 0;
                currentPartIndex = 0;
                displayString.Remove(0, displayString.Length);
                currentEvents.Clear();
            }
            else if (arguments.Count == 1)
            {
                // Default dequeue all occurences
                DequeueTextEvent(arguments[0], true);
            }
            else if (arguments.Count == 2)
            {
                if (arguments[1] == "true" || arguments[1] == "false")
                {
                    DequeueTextEvent(arguments[0], Convert.ToBoolean(arguments[1], CultureInfo.InvariantCulture));
                }
                else
                {
                    DequeueTextEvent(arguments[0], true);
                }
            }
        }

        void QueueCountCommandExecute(IDebugCommandHost host, string command, IList<string> arguments)
        {
            if (currentEvents.Count > 0)
            {
                host.Echo("Current text event: " + currentEvents[0].ID + " FrameIndx: " + currentFrameIndex.ToString() + " Part:" + currentPartIndex.ToString() + " In queue: " + currentEvents.Count);
            }
            else
            {
                host.Echo("Current text event: [No Event] In queue: " + currentEvents.Count);
            }
        }
#endif
    }
}